1
|
|
|
"use strict"; |
2
|
|
|
|
3
|
|
|
/* ------------------------------------------------------------------------ */ |
4
|
|
|
|
5
|
|
|
const O = Object, |
|
|
|
|
6
|
|
|
isBrowser = (typeof window !== 'undefined') && (window.window === window) && window.navigator, |
7
|
|
|
lastOf = x => x[x.length - 1], |
8
|
|
|
getSource = require ('get-source'), |
9
|
|
|
partition = require ('./impl/partition'), |
10
|
|
|
asTable = require ('as-table') |
11
|
|
|
|
12
|
|
|
/* ------------------------------------------------------------------------ */ |
13
|
|
|
|
14
|
|
|
class StackTracey extends Array { |
15
|
|
|
|
16
|
|
|
constructor (input, offset) { |
17
|
|
|
|
18
|
|
|
const originalInput = input |
19
|
|
|
, isParseableSyntaxError = input && (input instanceof SyntaxError && !isBrowser) |
20
|
|
|
|
21
|
|
|
super () |
22
|
|
|
|
23
|
|
|
/* Fixes for Safari */ |
24
|
|
|
|
25
|
|
|
this.constructor = StackTracey |
26
|
|
|
this.__proto__ = StackTracey.prototype |
27
|
|
|
|
28
|
|
|
/* new StackTracey () */ |
29
|
|
|
|
30
|
|
|
if (!input) { |
31
|
|
|
input = new Error () |
32
|
|
|
offset = (offset === undefined) ? 1 : offset |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
/* new StackTracey (Error) */ |
36
|
|
|
|
37
|
|
|
if (input instanceof Error) { |
38
|
|
|
input = input[StackTracey.stack] || input.stack || '' |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/* new StackTracey (string) */ |
42
|
|
|
|
43
|
|
|
if (typeof input === 'string') { |
44
|
|
|
input = StackTracey.rawParse (input).slice (offset).map (StackTracey.extractEntryMetadata) |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/* new StackTracey (array) */ |
48
|
|
|
|
49
|
|
|
if (Array.isArray (input)) { |
50
|
|
|
|
51
|
|
|
if (isParseableSyntaxError) { |
52
|
|
|
|
53
|
|
|
const rawLines = module.require ('util').inspect (originalInput).split ('\n') |
54
|
|
|
, fileLine = rawLines[0].match (/^([^:]+):(.+)/) |
55
|
|
|
|
56
|
|
|
if (fileLine) { |
57
|
|
|
input.unshift ({ |
58
|
|
|
file: fileLine[1], |
59
|
|
|
line: fileLine[2], |
60
|
|
|
column: (rawLines[2] || '').indexOf ('^') + 1, |
61
|
|
|
sourceLine: rawLines[1], |
62
|
|
|
callee: '(syntax error)', |
63
|
|
|
syntaxError: true |
64
|
|
|
}) |
65
|
|
|
} |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
this.length = input.length |
69
|
|
|
input.forEach ((x, i) => this[i] = x) |
70
|
|
|
} |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
static extractEntryMetadata (e) { |
74
|
|
|
|
75
|
|
|
const fileRelative = StackTracey.relativePath (e.file || '') |
76
|
|
|
|
77
|
|
|
return O.assign (e, { |
78
|
|
|
|
79
|
|
|
calleeShort: e.calleeShort || lastOf ((e.callee || '').split ('.')), |
80
|
|
|
fileRelative: fileRelative, |
81
|
|
|
fileShort: StackTracey.shortenPath (fileRelative), |
82
|
|
|
fileName: lastOf ((e.file || '').split ('/')), |
83
|
|
|
thirdParty: StackTracey.isThirdParty (fileRelative) && !e.index |
84
|
|
|
}) |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
static shortenPath (relativePath) { |
88
|
|
|
return relativePath.replace (/^node_modules\//, '') |
89
|
|
|
.replace (/^webpack\/bootstrap\//, '') |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
static relativePath (fullPath) { |
93
|
|
|
return fullPath.replace (isBrowser ? window.location.href : (process.cwd () + '/'), '') |
94
|
|
|
.replace (/^.*\:\/\/?\/?/, '') |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
static isThirdParty (relativePath) { |
98
|
|
|
return (relativePath[0] === '~') || // webpack-specific heuristic |
99
|
|
|
(relativePath[0] === '/') || // external source |
100
|
|
|
(relativePath.indexOf ('node_modules') === 0) || |
101
|
|
|
(relativePath.indexOf ('webpack/bootstrap') === 0) |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
static rawParse (str) { |
105
|
|
|
|
106
|
|
|
const lines = (str || '').split ('\n') |
107
|
|
|
|
108
|
|
|
const entries = lines.map (line => { line = line.trim () |
109
|
|
|
|
110
|
|
|
var callee, fileLineColumn = [], native, planA, planB |
|
|
|
|
111
|
|
|
|
112
|
|
|
if ((planA = line.match (/at (.+) \((.+)\)/)) || |
113
|
|
|
(planA = line.match (/(.*)@(.*)/))) { |
114
|
|
|
|
115
|
|
|
callee = planA[1] |
116
|
|
|
native = (planA[2] === 'native') |
117
|
|
|
fileLineColumn = (planA[2].match (/(.*):(.+):(.+)/) || []).slice (1) } |
118
|
|
|
|
119
|
|
|
else if ((planB = line.match (/^(at\s+)*(.+):([0-9]+):([0-9]+)/) )) { |
120
|
|
|
fileLineColumn = (planB).slice (2) } |
121
|
|
|
|
122
|
|
|
else { |
123
|
|
|
return undefined } |
124
|
|
|
|
125
|
|
|
return { |
126
|
|
|
beforeParse: line, |
127
|
|
|
callee: callee || '', |
128
|
|
|
index: isBrowser && (fileLineColumn[0] === window.location.href), |
129
|
|
|
native: native || false, |
130
|
|
|
file: fileLineColumn[0] || '', |
131
|
|
|
line: parseInt (fileLineColumn[1] || '', 10) || undefined, |
132
|
|
|
column: parseInt (fileLineColumn[2] || '', 10) || undefined } }) |
133
|
|
|
|
134
|
|
|
return entries.filter (x => (x !== undefined)) |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
withSource (i) { |
138
|
|
|
return this[i] && StackTracey.withSource (this[i]) |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
static withSource (loc) { |
142
|
|
|
|
143
|
|
|
if (loc.sourceFile || (loc.file && loc.file.indexOf ('<') >= 0)) { // skip things like <anonymous> and stuff that was already fetched |
144
|
|
|
return loc |
145
|
|
|
|
146
|
|
|
} else { |
147
|
|
|
let resolved = getSource (loc.file || '').resolve (loc) |
148
|
|
|
|
149
|
|
|
if (resolved.sourceFile) { |
150
|
|
|
resolved.file = resolved.sourceFile.path |
151
|
|
|
resolved = StackTracey.extractEntryMetadata (resolved) |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
if (resolved.sourceLine && resolved.sourceLine.includes ('// @hide')) { |
155
|
|
|
resolved.sourceLine = resolved.sourceLine.replace ('// @hide', '') |
156
|
|
|
resolved.hide = true |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
return O.assign ({ sourceLine: '' }, loc, resolved) |
160
|
|
|
} |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
get withSources () { |
164
|
|
|
return new StackTracey (this.map (StackTracey.withSource)) |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
get mergeRepeatedLines () { |
168
|
|
|
return new StackTracey ( |
169
|
|
|
partition (this, e => e.file + e.line).map ( |
170
|
|
|
group => { |
171
|
|
|
return group.items.slice (1).reduce ((memo, entry) => { |
172
|
|
|
memo.callee = (memo.callee || '<anonymous>') + ' → ' + (entry.callee || '<anonymous>') |
173
|
|
|
memo.calleeShort = (memo.calleeShort || '<anonymous>') + ' → ' + (entry.calleeShort || '<anonymous>') |
174
|
|
|
return memo }, O.assign ({}, group.items[0])) })) |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
get clean () { |
178
|
|
|
return this.withSources.mergeRepeatedLines.filter ((e, i) => (i === 0) || !(e.thirdParty || e.hide)) |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
at (i) { |
182
|
|
|
return O.assign ({ |
183
|
|
|
|
184
|
|
|
beforeParse: '', |
185
|
|
|
callee: '<???>', |
186
|
|
|
index: false, |
187
|
|
|
native: false, |
188
|
|
|
file: '<???>', |
189
|
|
|
line: 0, |
190
|
|
|
column: 0 |
191
|
|
|
|
192
|
|
|
}, this[i]) |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
static locationsEqual (a, b) { |
196
|
|
|
return (a.file === b.file) && |
197
|
|
|
(a.line === b.line) && |
198
|
|
|
(a.column === b.column) |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
get pretty () { |
202
|
|
|
|
203
|
|
|
return asTable (this.withSources.map ( |
204
|
|
|
e => [ ('at ' + e.calleeShort.slice (0, 30)), |
205
|
|
|
(e.fileShort && (e.fileShort + ':' + e.line)) || '', |
206
|
|
|
((e.sourceLine || '').trim () || '').slice (0, 80) ])) |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
static resetCache () { |
210
|
|
|
|
211
|
|
|
getSource.resetCache () |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
get asArray () { |
215
|
|
|
|
216
|
|
|
} |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/* Chaining helper for .isThirdParty |
220
|
|
|
------------------------------------------------------------------------ */ |
221
|
|
|
|
222
|
|
|
(() => { |
223
|
|
|
|
224
|
|
|
const methods = { |
225
|
|
|
|
226
|
|
|
include (pred) { |
227
|
|
|
|
228
|
|
|
const f = StackTracey.isThirdParty |
229
|
|
|
O.assign (StackTracey.isThirdParty = (path => f (path) || pred (path)), methods) |
230
|
|
|
}, |
231
|
|
|
|
232
|
|
|
except (pred) { |
233
|
|
|
|
234
|
|
|
const f = StackTracey.isThirdParty |
235
|
|
|
O.assign (StackTracey.isThirdParty = (path => f (path) && !pred (path)), methods) |
236
|
|
|
}, |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
O.assign (StackTracey.isThirdParty, methods) |
240
|
|
|
|
241
|
|
|
}) () |
242
|
|
|
|
243
|
|
|
/* Array methods |
244
|
|
|
------------------------------------------------------------------------ */ |
245
|
|
|
|
246
|
|
|
;['map', 'filter', 'slice', 'concat', 'reverse'].forEach (name => { |
247
|
|
|
|
248
|
|
|
StackTracey.prototype[name] = function (/*...args */) { // no support for ...args in Node v4 :( |
249
|
|
|
|
250
|
|
|
const arr = Array.from (this) |
251
|
|
|
return new StackTracey (arr[name].apply (arr, arguments)) |
252
|
|
|
} |
253
|
|
|
}) |
254
|
|
|
|
255
|
|
|
/* A private field that an Error instance can expose |
256
|
|
|
------------------------------------------------------------------------ */ |
257
|
|
|
|
258
|
|
|
StackTracey.stack = /* istanbul ignore next */ (typeof Symbol !== 'undefined') ? Symbol.for ('StackTracey') : '__StackTracey' |
|
|
|
|
259
|
|
|
|
260
|
|
|
/* ------------------------------------------------------------------------ */ |
261
|
|
|
|
262
|
|
|
module.exports = StackTracey |
263
|
|
|
|
264
|
|
|
/* ------------------------------------------------------------------------ */ |
265
|
|
|
|
266
|
|
|
|